简介
在JavaScript前端技术大行其道的今天,我们通常只需在后台构建API提供给前端调用,并且后端仅仅设计为给前端移动App调用。
用户认证是Web应用的重要组成部分,基于API的用户认证有两个最佳解决方案 —— OAuth 2.0 和 JWT(JSON Web Token)。
JWT 是 JSON Web Token 的缩写,是一个非常轻巧的规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。
- 降地耦合性,取代session,进一步实现前后端分离
- 减少服务器的压力
- 可以很简单的实现单点登录
JWT 由头部(header)、载荷(payload)与签名(signature)组成
- 头部申明了加密算法;
- 载荷中有两个比较重要的数据,exp 是过期时间,sub 是 JWT 的主体,这里就是用户的 id;
- 最后的 signature 是由服务器进行的签名,保证了 token 不被篡改。
服务器会保存一个全局的JWT_SECRET , 用于生成token和验证token,对于多服务器,不用每个都存储token,而只需要知道解密秘钥,
即可拥有验证用户身份的能力
JWT流程
使用JWT的应用基本都遵循下面的使用流程:
- 访问登录点。
- 登录服务验证之后,签发证书返回给客户端。
- 客户端保存证书,并在每次请求时将其附在request header中,表示已经登录。
- 服务器接收请求之后,通过签名和时戳,验证Token的有效性。
- 若有效,则响应客户端。
对应的request header如下:
1 Authorization:Bearer <token>
安装
jwt-auth 是 Laravel 和 lumen 的JWT组件
Laravel 5.5 的适配版本为 1.0.0-rc.2
1
composer require tymon/jwt-auth:1.0 -rc
创建 JWT 的 secret,用于最后的签名,更换这个secret 会导致之前生成的所有 token 无效。
1
php artisan jwt:secret
此时会在.env文件中,增加了一行 JWT_SECRET
修改 config/auth.php,将 api guard 的 driver 改为 jwt
1
2
3
4
5
6
7
8
9
10
11'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],修改 config/api.php,auth 中增加 JWT 相关的配置
1
2
3'auth' => [
'jwt' => 'Dingo\Api\Auth\Provider\JWT',
],user 模型需要继承 Tymon\JWTAuth\Contracts\JWTSubject 接口,
并实现接口的两个方法 getJWTIdentifier() 和 getJWTCustomClaims()。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
// 返回了 User 的 id
public function getJWTIdentifier()
{
return $this->getKey();
}
// 额外在JWT 载荷中增加的自定义内容
public function getJWTCustomClaims()
{
return [];
}
}
用法
- attempt()
通过用户身份验证,并返回token1
2
3
4
5
6$credentials['phone'] = $username;
$credentials['password'] = $request->password;
if (!$token = \Auth::guard('api')->attempt($credentials)) {
return $this->response->errorUnauthorized('用户名或密码错误');
}
attempt() 位置在 \vendor\tymon\jwt-auth\src\JWTGuard.php中
- fromUser()
根据用户生成token1
$token = Auth::guard('api')->fromUser($user);
fromUser() 位置在 vendor\tymon\jwt-auth\src\JWT.php
login()也调用fromUser()
refresh()
刷新token1
$token = Auth::guard('api')->refresh();
invalidate()
把token加入黑名单,使之无效。,其实黑名单其实就是将 Token 放入缓存,这样验证这个 Token 的时候,
就会去黑名单(缓存)中看看,如果黑名单中有,则验证失败,同样的,登出(logout)方法也是将 Token 加入黑名单logout()
退出登录1
Auth::guard('api')->logout();
内部调用了invalidate()
- 过期时间
1
\Auth::guard('api')->factory()->getTTL()
单位是分钟
token 的三个时间
有效时间:
有效时间指的的是你获得 token 后,在多少时间内可以凭这个 token 去获取内容,逾时无效。1
2// 单位:分钟
'ttl' => env('JWT_TTL', 60),
刷新时间
刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token1
2// 单位:分钟
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
注意: 可以把 时间写在env文件中
注意
如果同一时间只允许登录唯一一台设备,我们可以在用户表中新加一个字段,先将用户的token存下来,下次用户再次登录时,
调用如下代码将旧的token加入黑名单即可。1
\JWTAuth::setToken($oldToken)->invalidate();
然后将新的 Token 保存下来,最后将 Token 返回。
如果这个用户长时间没登录过,Token 已无效。再用这个 oldToken 去加入黑名单会报错。可以如下:1
2
3
4
5
6// 检查旧 Token 是否有效
Auth::guard('api')->setToken($oldToken);
if(Auth::guard('api')->check()){
// 加入黑名单
Auth::guard('api')->invalidate();
}